#include <cassert>
#include "herm/base/utils/misc.h"
#include "buffer.h"

namespace herm{

Buffer::Buffer(unsigned int size)
{
	// size must be a power of 2! 
	if (!IsPowerOf2(size))
		size = RoundUpPowerOf2(size);

	if (size == 0)
		size = 2;

	m_buff = new char[size];
	m_size = size;
	m_write = m_read = 0;
}

Buffer::~Buffer()
{
	delete[] m_buff;
	m_buff = nullptr;
}

void Buffer::Clear()
{
//	if (m_write != 0 || m_read != 0)
//		assert(false);
	m_write = m_read = 0;
}

unsigned int Buffer::Peek(char* buff, unsigned int len)
{
	unsigned int size = m_write - m_read;
	if (size == 0)
		return 0;

	len = std::min(len, size);

	smp_rmb();

	// first get the data from m_read until the end of the buffer 
	unsigned int len2 = std::min(len, m_size - (m_read & (m_size - 1)));
	memcpy(buff, m_buff + (m_read & (m_size - 1)), len2);
	// then get the rest (if any) from the beginning of the buffer 
	memcpy(buff + len2, m_buff, len - len2);

	smp_mb();

	//// No move m_read ////

	return len;
}

bool Buffer::SeekReadPtr(unsigned int len)
{
	if (Length() < len)
		return false;

	m_read += len;
	return true;
}

unsigned int Buffer::Fetch(char* buff, unsigned int len)
{
	unsigned int size = m_write - m_read;
	if (size == 0)
		return 0;

	len = std::min(len, size);

	smp_rmb();

	// first get the data from m_read until the end of the buffer 
	unsigned int len2 = std::min(len, m_size - (m_read & (m_size - 1)));
	memcpy(buff, m_buff + (m_read & (m_size - 1)), len2);
	// then get the rest (if any) from the beginning of the buffer 
	memcpy(buff + len2, m_buff, len - len2);

	smp_mb();

	m_read += len;

	return len;
}

unsigned int Buffer::FetchChunk(char *buff, unsigned int len)
{
	if (m_write - m_read < len)
		return 0;

	smp_rmb();

	return Fetch(buff, len);
}

bool Buffer::Push(const char* buff, unsigned int len)
{
	if (Space() < len)
		return false;

	len = std::min(len, Space());

	// Ensure that we sample the m_read index -before- we
	// start putting bytes into the buffer.
	smp_mb();

	// first put the data starting from m_write to buffer end 
	unsigned int l = std::min(len, m_size - (m_write & (m_size - 1)));
	memcpy(m_buff + (m_write & (m_size - 1)), buff, l);

	// then put the rest (if any) at the beginning of the buffer 
	memcpy(m_buff, buff + l, len - l);

	// Ensure that we add the bytes to the buffer -before-
	// we update the m_write index.
	smp_wmb();

	m_write += len;

	return true;
}

void Buffer::PushBlock(const char* buff, unsigned int len)
{
	while (true)
	{
		if (!Push(buff, len))
		{
			herm::Sleep(50);
		}
	}
}

bool Buffer::IsPowerOf2(unsigned int x)
{
	return (x != 0) && ((x & (x - 1)) == 0);
}

SyncBuffer::SyncBuffer(unsigned int size)
{
	m_buffer = new Buffer(size);
}

SyncBuffer::~SyncBuffer()
{
	delete m_buffer;
}

unsigned int SyncBuffer::Peek(char* buff, unsigned int len)
{
	std::lock_guard<std::mutex> guard(m_mutex);

	return m_buffer->Peek(buff, len);
}

bool SyncBuffer::SeekReadPtr(unsigned int len)
{
	std::lock_guard<std::mutex> guard(m_mutex);

	return m_buffer->SeekReadPtr(len);
}

unsigned int SyncBuffer::Fetch(char *buff, unsigned int len)
{
	std::lock_guard<std::mutex> guard(m_mutex);

	return m_buffer->Fetch(buff, len);
}

unsigned int SyncBuffer::FetchChunk(char *buff, unsigned int len)
{
	std::lock_guard<std::mutex> guard(m_mutex);

	return m_buffer->FetchChunk(buff, len);
}

bool SyncBuffer::Push(const char *buff, unsigned int len)
{
	std::lock_guard<std::mutex> guard(m_mutex);

	return m_buffer->Push(buff, len);
}
}
